/**
 * TCF用例转换器
 *
 * Created by snowingsea on 2021/01/20.
 */
const {
  TestCaseTransformer,
  VARIABLE_KIND,
  VARIABLE_TYPE,
  VARIABLE_SCOPE_KIND,
  STUB_KIND,
} = require('smart-rocket-unit');
const logger = require('../../../logger');
const utility = require('./../../../utility');

class TCFTestCaseTransformer extends TestCaseTransformer {
  /**
   * 构造函数
   * @param {Object} testCaseData 测试用例数据
   * @param {Object} variablesData 函数变量信息数据
   * @param {Object} typeSystemData 类型系统数据
   */
  constructor(testCaseData, variablesData, typeSystemData) {
    super(testCaseData, variablesData, typeSystemData);
    this.startups = []; // 初始化信息（指针目标数据）
    this.variables = []; // 变量信息（全局变量、形参）
    this.stubs = []; // 桩信息
    this.globals = []; // 全局变量数据
    this.globalStubs = []; // 桩数据
  }

  transform() {
    super.transform();

    // build startups
    this.buildVariableData(this.mallocVariablesData, this.startups);

    // build global variables
    this.buildVariableData(this.globalVariablesData, this.variables, 'G');
    // build param variables
    this.buildVariableData(this.paramVariablesData, this.variables, 'Z');
    // build return value
    this.buildVariableData(this.returnValueData, this.variables, 'O');
    // build global output variables
    this.buildVariableData(this.globalOutputData, this.variables, 'H');
    // build param output variables
    this.paramVariablesData.forEach((param) => {
      if (param.type === VARIABLE_TYPE.pointer) {
        this.variables.push({
          name: param.name,
          value: '?',
          type: param.typeName,
          usage: 'O',
        });
      }
    });

    // build stubs
    this.stubsData.forEach((stubData) => {
      if (stubData.currentStubKind === STUB_KIND.code) {
        // TODO: 桩代码，待实现
        return;
      }
      const { stubName } = stubData;
      this.buildStubDataByValue(stubName, stubData);
    });

    // build malloc global variables
    this.mallocVariablesData.forEach((mallocData) => {
      this.globals.push({
        name: mallocData.name,
        type: mallocData.elementTypeName,
        bounds: mallocData.length,
      });
    });
  }

  /**
   * 构建变量数据
   * @param {Object} data 用例数据
   * @param {Array} results tcf数据结构
   * @param {String} usage 使用标记，Z表示形参、G表示全局变量、O表示输出、H表示全局输出
   * @param {String} parentPath 上层路径
   */
  buildVariableData(data, results, usage = null, parentPath = '') {
    if (utility.isArrayType(data)) {
      data.forEach((item) => {
        this.buildVariableData(item, results, usage, parentPath);
      });
      return;
    }
    if (!utility.isObjectType(data)) {
      return;
    }
    // 数组
    if (data.type === VARIABLE_TYPE.array) {
      this.buildVariableData(data.children, results, usage, parentPath);
      return;
    }
    let currentPath = parentPath ? `${parentPath}.${data.name}` : data.name;
    if (currentPath === 'return') {
      currentPath = '%';
    }
    this.buildVariableData(data.children, results, usage, currentPath);
    let value;
    if (data.data) {
      value = data.data['@value'] || data.data['@result'];
    }
    switch (data.type) {
      case VARIABLE_TYPE.basic:
      case VARIABLE_TYPE.pointer:
      case VARIABLE_TYPE.functionPointer:
      case VARIABLE_TYPE.enumeration:
        if (!value && data.scope === VARIABLE_SCOPE_KIND.input) {
          value = '0';
        }
        break;
      default:
        break;
    }
    if (data.type === VARIABLE_TYPE.pointer
        && data.scope === VARIABLE_SCOPE_KIND.output) {
      value = '?';
    }
    if (value && !utility.isArrayType(value) && !utility.isObjectType(value)) {
      results.push({
        name: currentPath,
        value,
        type: data.typeName,
        usage,
      });
    }
  }

  /**
   * 构建桩用例数据
   * @param {String} stubName 桩名称
   * @param {Object} stubData 桩用例数据
   */
  buildStubDataByValue(stubName, stubData) {
    const stubValue = this.testCaseData.data.stubs[stubName]['@value'];
    if (!stubValue) {
      return;
    }
    const paramsData = {};
    const returnData = {};
    function indexSortBy(a, b) {
      return Number.parseInt(a.toString(), 10) - Number.parseInt(b.toString(), 10);
    }
    Object.keys(stubValue).sort(indexSortBy).forEach((time) => {
      stubData.children = [];
      this.addStubOutputFields(stubName, time, stubData.children);
      // 筛选桩指针目标
      const stubPointerTargets = {};
      stubData.children.forEach((child) => {
        if (child.kind === VARIABLE_KIND.stubPointerTarget) {
          stubPointerTargets[child.name] = child;
        }
      });

      stubData.children.forEach((child) => {
        switch (child.kind) {
          case VARIABLE_KIND.stubReturn:
            this.buildStubReturnData(child, returnData, time);
            break;
          case VARIABLE_KIND.stubParam:
            this.buildStubParamData(child, stubPointerTargets, paramsData, time);
            break;
          default:
            break;
        }
      });
    });

    const params = Object.keys(paramsData).map((name) => paramsData[name]);
    const returnValue = Object.keys(returnData).map((name) => returnData[name]);
    if (params.length === 0 && returnValue.length === 0) {
      return;
    }
    let overloading = [];
    if (this.variablesData.stubs[stubName].params) {
      overloading = this.variablesData.stubs[stubName].params.map((item) => item['@type']);
    }
    this.globalStubs.push({
      name: stubName,
      overloading,
    });
    this.stubs.push({
      name: stubName,
      overloading,
      params,
      returnValue,
    });
  }

  /**
   * 构建桩返回值用例数据
   * @param {Object} stubReturnData 桩返回值用例数据
   * @param {Object} returnData TCF桩返回值用例数据结构
   * @param {Number} time 调用次数
   */
  buildStubReturnData(stubReturnData, returnData, time) {
    const results = [];
    this.buildVariableData(stubReturnData, results);
    results.forEach((result) => {
      const { name } = result;
      if (!returnData[name]) {
        returnData[name] = {
          name,
          type: result.type,
          values: [],
        };
      }
      for (let i = returnData[name].values.length; i < time; i += 1) {
        returnData[name].values.push('');
      }
      returnData[name].values.push(result.value);
    });
  }

  /**
   * 构建桩参数用例数据
   * @param {Object} stubParamData 桩参数用例数据
   * @param {Object} stubPointerTargets 桩指针目标数据集合
   * @param {Object} paramsData TCF桩参数数据结果
   * @param {Number} time 调用次数
   */
  buildStubParamData(stubParamData, stubPointerTargets, paramsData, time) {
    const stubParams = [];
    this.buildVariableData(stubParamData, stubParams);
    stubParams.forEach((param) => {
      if (!param.value || !param.value.startsWith('@')) {
        return;
      }
      const value = param.value.substr(1);
      if (!stubPointerTargets[value]) {
        logger.error(`Can't find ${value} in Stub Pointer Targets: ${stubPointerTargets}`);
        return;
      }
      stubPointerTargets[value].name = param.name;
      const results = [];
      this.buildVariableData(stubPointerTargets[value], results);
      results.forEach((result) => {
        const name = utility.replaceAll(result.name, value, stubParamData.name);
        if (!paramsData[name]) {
          paramsData[name] = {
            name,
            type: result.type,
            values: [],
          };
        }
        for (let i = paramsData[name].values.length; i < time; i += 1) {
          paramsData[name].values.push('');
        }
        paramsData[name].values.push(result.value);
      });
    });
  }
}

module.exports = TCFTestCaseTransformer;
